package com.weixin.gong.example.guava;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.cache.Weigher;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.util.concurrent.UncheckedExecutionException;
import org.junit.Assert;
import org.junit.Test;

import java.util.Map;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

/**
 * Created at 2017/7/14 17:37
 *
 * @author gongweixin
 */
public class CacheExample {

    @Test
    public void exists() throws Exception {
        //LoadingCache调用Present方法不会触发load方法
        LoadingCache<String, String> cache = CacheBuilder.newBuilder()
                .maximumSize(1000)
                .expireAfterWrite(10, TimeUnit.MINUTES)
                .build(new CacheLoader<String, String>() {
                    public String load(String key) throws Exception {
                        return key;
                    }
                });

        Assert.assertNull(cache.getIfPresent("1"));
        Assert.assertNotNull(cache.getUnchecked("1"));

        Assert.assertEquals(cache.getAllPresent(Lists.newArrayList("1", "2")).size(), 1);
        Assert.assertEquals(cache.getAll(Lists.newArrayList("1", "2")).size(), 2);
    }

    @Test
    public void waitLoadCache() throws Exception {
        //LoadingCache被多个线程同时访问一个未命中的key，这些线程会阻塞，load方法只会被执行一次
        final LoadingCache<String, String> cache = CacheBuilder.newBuilder()
                .maximumSize(1000)
                .expireAfterWrite(10, TimeUnit.MINUTES)
                .build(new CacheLoader<String, String>() {
                    public String load(String key) throws Exception {
                        System.out.println(Thread.currentThread() + ":" + key);
                        if (Thread.currentThread().getName().equals("thread-1")) {
                            Thread.sleep(5000);
                        }
                        return key;
                    }
                });

        new Thread(new Runnable() {
            public void run() {
                System.out.println(Thread.currentThread().getName() + ":" + cache.getUnchecked("1"));
            }
        }, "thread-1").start();

        Thread.sleep(1000);

        new Thread(new Runnable() {
            public void run() {
                System.out.println(Thread.currentThread().getName() + ":" + cache.getUnchecked("1"));
            }
        }, "thread-2").start();

        new Thread(new Runnable() {
            public void run() {
                System.out.println(Thread.currentThread().getName() + ":" + cache.getUnchecked("3"));
            }
        }, "thread-3").start();

        new Thread(new Runnable() {
            public void run() {
                try {
                    System.out.println(Thread.currentThread().getName() + ":" + cache.getAll(Lists.newArrayList("3", "1")));
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
            }
        }, "thread-4").start();

        Thread.sleep(5000);
    }

    @Test
    public void loadAll() throws Exception {
        //LoadingCache调用loadAll获取多个key的时候时候，已经存在缓存的key会执行从缓存中获取，不存在的key会挨个调用load方法获取
        //如果重载loadAll方法，不存在的key会作为一个迭代器调用重载的loadAll方法，loadAll方法要求返回的map中包含所有传入的key，否者会抛异常
        final LoadingCache<String, String> cache = CacheBuilder.newBuilder()
                .maximumSize(1000)
                .expireAfterWrite(10, TimeUnit.MINUTES)
                .build(new CacheLoader<String, String>() {

                    public String load(String key) throws Exception {
                        if (Thread.currentThread().getName().equals("thread-1")) {
                            Thread.sleep(5000);
                        }
                        return key;
                    }

                    @Override
                    public Map<String, String> loadAll(Iterable<? extends String> keys) throws Exception {
                        Thread.sleep(2000);
                        Map<String, String> map = Maps.newHashMap();
                        for (String key : keys) {
                            map.put(key, key);
                        }
                        System.out.println("load:" + map);
                        return map;
                    }
                });

        new Thread(new Runnable() {
            public void run() {
                System.out.println(Thread.currentThread().getName() + ":" + cache.getUnchecked("1"));
            }
        }, "thread-1").start();

        Thread.sleep(1000);

        new Thread(new Runnable() {
            public void run() {
                try {
                    System.out.println(cache.getAllPresent(Lists.newArrayList("1", "2")));
                    System.out.println(cache.getAll(Lists.newArrayList("1", "2")));
                    System.out.println(cache.getAll(Lists.newArrayList("1", "2", "3", "4")));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, "thread-2").start();

        Thread.sleep(1000);

        new Thread(new Runnable() {
            public void run() {
                System.out.println(Thread.currentThread().getName() + ":" + cache.getUnchecked("2"));
            }
        }, "thread-3").start();

        Thread.sleep(5000);
    }

    @Test
    public void getCall() throws Exception {
        //get(key,callable)会将callable获得的值放入缓存中
        Cache<String, String> cache = CacheBuilder.newBuilder()
                .maximumSize(1000)
                .expireAfterWrite(10, TimeUnit.MINUTES)
                .build();

        Assert.assertEquals(cache.get("1", new Callable<String>() {
            public String call() throws Exception {
                System.out.println("call : 1");
                return "1";
            }
        }), "1");
        Assert.assertEquals("1", cache.getIfPresent("1"));
    }

    @Test
    public void testReferWrite() throws ExecutionException, InterruptedException {
        //refreshAfterWrite并不是准确的，实时的，是延后的，当发生get的时候才会check
        LoadingCache<String, String> cache = CacheBuilder.newBuilder()
                .refreshAfterWrite(1, TimeUnit.SECONDS)
                .build(new CacheLoader<String, String>() {
                    @Override
                    public String load(String key) throws Exception {
                        System.out.println("time:" + System.currentTimeMillis() / 1000);
                        return key;
                    }
                });
        System.out.println(cache.get("key"));
        Thread.sleep(3000L);
        System.out.println("3 seconds");
        System.out.println(cache.get("key"));
        Thread.sleep(100L);
        System.out.println("0.1 seconds");
        cache.refresh("key");
    }

    @Test
    public void testMinCapacity() throws Exception {
        //容量及小时，不够存放一次loadAll的缓存值，会保留一部分的缓存
        LoadingCache<String, String> cache = CacheBuilder.newBuilder()
                .maximumSize(1)
                .expireAfterWrite(10, TimeUnit.MINUTES)
                .build(new CacheLoader<String, String>() {
                    public String load(String key) throws Exception {
                        return key;
                    }

                    @Override
                    public Map<String, String> loadAll(Iterable<? extends String> keys) throws Exception {
                        Map<String, String> map = Maps.newHashMap();
                        for (String key : keys) {
                            map.put(key, key);
                        }
                        System.out.println("do loadAll, map:" + map);
                        return map;
                    }
                });

        System.out.println(cache.getAll(Lists.newArrayList("1", "2")));
        System.out.println(cache.getAllPresent(Lists.newArrayList("1", "2")));
        System.out.println(cache.getAll(Lists.newArrayList("1", "2")));
        System.out.println(cache.getAllPresent(Lists.newArrayList("1", "2")));
    }

    @Test
    public void testWeight() throws Exception {
        LoadingCache<String, String> cache = CacheBuilder.newBuilder()
                .maximumWeight(1)
                .expireAfterWrite(10, TimeUnit.MINUTES)
                .weigher(new Weigher<String, String>() {
                    public int weigh(String key, String value) {
                        return 0;
                    }
                })
                .build(new CacheLoader<String, String>() {
                    public String load(String key) throws Exception {
                        return key;
                    }

                    @Override
                    public Map<String, String> loadAll(Iterable<? extends String> keys) throws Exception {
                        Map<String, String> map = Maps.newHashMap();
                        for (String key : keys) {
                            map.put(key, key);
                        }
                        System.out.println("do loadAll, map:" + map);
                        return map;
                    }
                });
    }

    @Test
    public void testCacheLoad() throws Exception {
        Cache<String, String> cache = CacheBuilder.newBuilder()
                .expireAfterWrite(10L, TimeUnit.MINUTES)
                .concurrencyLevel(10).softValues()
                .build();
        Assert.assertEquals("1", cache.get("1", new Callable<String>() {
            public String call() throws Exception {
                Thread.sleep(5000L);
                return "1";
            }
        }));
    }

    @Test
    public void testCacheException() throws Exception {
        final Cache<String, String> cache = CacheBuilder.newBuilder()
                .expireAfterWrite(10L, TimeUnit.MINUTES)
                .concurrencyLevel(10).softValues()
                .build();

        Runnable runnable = new Runnable() {
            public void run() {
                try {
                    System.out.println(Thread.currentThread().getName());
                    cache.get("1", new Callable<String>() {
                        public String call() throws Exception {
                            Thread.sleep(1000L);
                            System.out.println("do call" + Thread.currentThread().getName());
                            throw new RuntimeException("123");
                        }
                    });
                } catch (Exception e) {
                    try {
                        Thread.sleep(100 + new Random().nextInt(100));
                    } catch (InterruptedException e1) {
                        e1.printStackTrace();
                    }
                    e.printStackTrace();
                    System.out.println(Thread.currentThread().getName());
                }
            }
        };

        new Thread(runnable, "thread-1").start();
        new Thread(runnable, "thread-2").start();

        Thread.sleep(3000);
        System.out.println(cache.getIfPresent("1"));
        try {
            cache.get("1", new Callable<String>() {
                public String call() throws Exception {
                    Thread.sleep(1000L);
                    System.out.println("do call" + Thread.currentThread().getName());
                    throw new RuntimeException("123");
                }
            });
        } catch (UncheckedExecutionException e) {
            e.getCause().printStackTrace();
        }

    }

    @Test//测试获取多个key的时候，传入空list的结果
    public void testGetEmpty() throws Exception {
        final Cache<String, String> cache = CacheBuilder.newBuilder()
                .expireAfterWrite(10L, TimeUnit.MINUTES)
                .concurrencyLevel(10).softValues()
                .build();

        ImmutableMap<String, String> map = cache.getAllPresent(Lists.newArrayList());
        Assert.assertNotNull(map);
    }
}
